/******************************************************************************************************
Copyright (C) 2013 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*******************************************************************************************************/

/*globals Application, ByteArray, DBPlugin, Dir, File, FileDialog, MessageBox, Molecule, molecule,
  MnUi, ProgressDialog, Resumable, Str, TextStream, dbOpenDatabase, dbMakeDBItems, print, qsTr, serialization, settings*/
/*jslint plusplus: true, indent: 4*/

// Parse the SDFile with the given path and count the molecules it contains. Will show message and return 0 on error.
function dbImportSDFileCountMolecules(inFilePath) {
    "use strict";
    // Open File
    var inFile = new File(inFilePath);
    if (!inFile.open(File.ReadOnly)) {
        MessageBox.critical(qsTr("Could not open file '{0}'.").format(inFilePath));
        return 0;
    }

    // Set Application in busy mode
    Application.mainWindow.setBusy(qsTr("Working..."));

    // Open Progress Dialog
    var progress = new ProgressDialog(),
    progressLabel = qsTr("Reading file...\n{0} Molecules\n{1} of {2} bytes read"),
    progressCanceled = false;
    progress.setRange(0, inFile.size);
    progress.value = 0;
    progress.labelText = progressLabel.format(0, 0, inFile.size);
    progress.show();

    // Count Molecules
    var inStream = new TextStream(inFile),
     startTime = new Date(),
     token = "$$$$",
     chunkLength = 10000000, 
     charSize = new ByteArray("c", inStream.codec).size,
     readedChunk,
     readedSize = 0,
     numMolecules = 0;

    while (!inStream.atEnd() && !progressCanceled) {
        readedChunk = inStream.read(chunkLength);
        while(readedChunk[readedChunk.length - 1] == token[0]){ // to avoid broken tokens in the buffer
            readedChunk += inStream.read(1);    
        }
        var j = 0;
        while ((j >= 0) && !progress.wasCanceled){
            j = readedChunk.indexOf(token,j);
            if (j >= 0){
                numMolecules++;
                j = j + token.length;     
            }
        }
        readedSize += charSize * readedChunk.length;
        progress.value = readedSize;
        progress.labelText = progressLabel.format(numMolecules, readedSize, inFile.size);
        Application.mainWindow.processEvents(); 
        progressCanceled = progress.wasCanceled;  
    }


    inFile.close();
    progress.close();
    Application.mainWindow.endBusy();
    print("Readed " + numMolecules +" molecules in " + ((new Date() - startTime) / 1000) + " seconds.");

    // Need to check wether the progress dialog was canceled before closing it
    if (progressCanceled) {
        numMolecules = 0;
    } else if (numMolecules === 0) {
        MessageBox.critical(qsTr("Could not find any molecules to import."));
    }
    
    return numMolecules;
}

function writeToFile(aFilePath, aText) {
    file = new File(aFilePath);
    file.open(File.WriteOnly);
    stream = new TextStream(file);
    stream.write(aText);
    file.close();
}

function dbImportSDFileMolecule(aFileName, aMolNumber, aMolText, aDocument, aDbDef, aExitError) {
    "use strict";

    var logTimes = false;
    if (logTimes) print("importMolecule({0}) :: [Begin]".format(aMolNumber));
    var cid = 0, startTime = new Date(), spStartTime = new Date();

    try {
        // Clear Document
        spStartTime = new Date();
        for (var i = 0; i < aDocument.itemCount(); i++) {
            Document.deleteItems(aDocument.item(i));
        }
        if (logTimes) print("importMolecule({0}) :: clear document in {1} seconds".format(aMolNumber, ((new Date() - spStartTime) / 1000) ));

        // Import MolFile
        spStartTime = new Date();
        var moleculeUUID = molecule.importMolfile(aMolText);
        var moleculeItem = new Molecule(aDocument.getItem(moleculeUUID));
        if (moleculeItem === undefined || moleculeItem.uuid === undefined) {
            throw qsTr("No molecule could be read from section {0}.").format(aMolNumber);
        }
        if (logTimes) print("importMolecule({0}) :: molfile imported in {1} seconds".format(aMolNumber, ((new Date() - spStartTime) / 1000) ));

        // If the sdf data contains a list of ms peaks, create a Mass spectrum object
        spStartTime = new Date();
        var msPeaks = moleculeItem.getSDFData("MASS SPECTRAL PEAKS");
        if (msPeaks) {
            var msFilePath = Dir.temp() + "/" + aFileName + "-" + aMolNumber.toString().lpad("0", 6) + ".csv";
            writeToFile(msFilePath, msPeaks);
            if (!serialization.open(msFilePath, "MS CSV File (*.csv *.txt)")) {
                throw qsTr("Error loading MS Spectrum from {0}").format(msFilePath);
            }
            File.remove(msFilePath);
        }
        if (logTimes) print("importMolecule({0}) :: mass item imported in {1} seconds".format(aMolNumber, ((new Date() - spStartTime) / 1000) ));

        // Save to DB
        spStartTime = new Date();
        var dbItems = dbMakeDBItems(DBPlugin.client, aDbDef, true, false);
        if (dbItems.length === 0) {
            throw qsTr("Error creating database items from section {0}.").format(aMolNumber);
        }
        cid = DBPlugin.addRecord(DBPlugin.client, dbItems);
        if (cid <= 0) {
            throw qsTr("Error while saving molecule {0} to database.").format(aMolNumber);
        }
        if (logTimes) print("importMolecule({0}) :: saved to db in {1} seconds".format(aMolNumber, ((new Date() - spStartTime) / 1000) ));
    } catch (err) {
        aExitError.push(err);
    }
    spStartTime = new Date();

    if (logTimes) print("importMolecule({0}) :: closed document in {1} seconds".format(aMolNumber, ((new Date() - spStartTime) / 1000) ));

    // Return new cid
    if (logTimes) print("importMolecule({0}) :: [End] in {1} seconds".format(aMolNumber, ((new Date() - startTime) / 1000) ));
    return cid;
}

function dbImportSDFile() {
    "use strict";

    // Prompt for file to import
    var inDirSettingsKey = "Database/Import SDFile/Input Directory";
    var inFilePath = FileDialog.getOpenFileName("SDfile (*.sdf);;All files (*.*)", qsTr("Select File"), settings.value(inDirSettingsKey, Dir.home()));
    if (!inFilePath.length) {
        return;
    }
    settings.setValue(inDirSettingsKey, File.absDirPath(inFilePath));

    // Choose database
    var dbname = dbOpenDatabase();
    if (!dbname) {
        return;
    }
    var dbDef = DBPlugin.client.definition(DBPlugin.client.database);

    // First run: Count molecules
    var numMolecules = dbImportSDFileCountMolecules(inFilePath);
    if (numMolecules <= 0) {
        return;
    }

    // Open file
    var inFile = new File(inFilePath);
    var inFileName = Str.fileNameFromPath(inFilePath);
    if (!inFile.open(File.ReadOnly)) {
        MessageBox.critical(qsTr("Could not open file '{0}'.").format(inFilePath));
        return;
    }

    // Show resume dialog
    var resumeSettingsKey = "Database/Import SDFile/Resume";
    var resumable = new Resumable(resumeSettingsKey, 1, numMolecules, qsTr("Import Molecules"));
    if (!resumable.init()) {
        return;
    }

    // Open Progress Dialog
    var progress = new ProgressDialog();
    progress.setRange(0, resumable.maxValue - resumable.minValue + 1);
    progress.value = 0;
    progress.labelText = qsTr("Processing...");
    progress.show();

    // Process file
    var line, errFilePath, moleculeText, cid, 
        inStream = new TextStream(inFile), 
        buffer = [], 
        endConnectionTableWritten = false,
        ok = true, 
        molCount = 0, 
        successCount = 0,
        startTime = new Date(), 
        importError = [],
        dw = Application.mainWindow.newDocument(),
        token = "$$$$",
        chunkLength = 2000000,
        readedChunk,
        lines = [],
        l = 0;

    while (!inStream.atEnd() && ok && !progress.wasCanceled && molCount <= resumable.maxValue) {
        // Read a Chunk of Lines
        readedChunk = inStream.read(chunkLength);
        while(readedChunk[readedChunk.length - 1] == token[0]){ // to avoid broken tokens in the buffer
            readedChunk += inStream.read(1);    
        }
        readedChunk = readedChunk.replace(/\r\n/g,"\n");
        lines = readedChunk.split("\n");    
        // process each line
        l = 0;
        while ((l < lines.length) && !progress.wasCanceled && molCount <= resumable.maxValue){
            line = lines[l];
            if (line === token) {
                molCount++;
                if (molCount >= resumable.minValue && molCount <= resumable.maxValue) {
                    // EGW1539: Add "M  END" to connection table
                    if (!endConnectionTableWritten) {
                        buffer.push("M  END");
                    }
    
                    // Import molecule
                    moleculeText = buffer.join("\n");
                    importError = [];
                    cid = dbImportSDFileMolecule(inFileName, molCount, moleculeText, dw, dbDef, importError);
                    if (cid > 0){
                        successCount++;
                        resumable.setValue(successCount + 1);
                        progress.labelText = qsTr("Molecule {0}: Saved to record {1}.").format(molCount, cid);
                    }else{
                        Application.mainWindow.endBusy();
                        errFilePath = Dir.temp() + "/" + inFileName + "-" + molCount.toString().lpad("0", 6) + ".sdf";
                        writeToFile(errFilePath, moleculeText);
                        MessageBox.warning(importError + "\n\n" + qsTr("The section has been left in {0}.").format(errFilePath));
                        ok = (MessageBox.question(qsTr("Continue with Import?"), MessageBox.Yes, MessageBox.No) === MessageBox.Yes);
                        Application.mainWindow.setBusy(qsTr("Working..."));
                    }
    
                    progress.value++;
                    Application.mainWindow.processEvents();
                }
                endConnectionTableWritten = false;
                buffer = [];
            } else if (line === "M END" || line === "M  END") {
                buffer.push("M  END");
                endConnectionTableWritten = true;
            } else {
                if (line.substr(0, 1) === ">" && !endConnectionTableWritten) {
                    // EGW1539: Add "M END" to connection table
                    buffer.push("M  END");
                    endConnectionTableWritten = true;
                }
                buffer.push(line);
            }
            l++;
        }
    }
    dw.close();
    print("All done in " + ((new Date() - startTime) / 1000) + " seconds.");

    // Close Dialogs
    inFile.close();
    if (ok && !progress.wasCanceled) {
        resumable.setValue(0);
    }
    progress.close();
    Application.mainWindow.endBusy();
    MessageBox.information(qsTr("Imported <b>{0}</b> molecules into database <b>{1}</b>.").format(successCount, dbname));
}

if (this.MnUi && MnUi.scripts_db) {
    MnUi.scripts_db.scripts_db_ImportSDFile = dbImportSDFile;
}